package org.geektimes.projects.user.repository;

import org.apache.commons.lang.StringUtils;
import org.geektimes.function.ThrowableFunction;
import org.geektimes.projects.user.domain.User;
import org.geektimes.projects.user.sql.DBConnectionManager;

import javax.annotation.Resource;
import javax.persistence.EntityManager;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.*;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;

import static org.apache.commons.lang.ClassUtils.wrapperToPrimitive;

public class DatabaseUserRepository implements UserRepository {

    private static Logger logger = Logger.getLogger(DatabaseUserRepository.class.getName());

    /**
     * 通用处理方式
     */
    private static Consumer<Throwable> COMMON_EXCEPTION_HANDLER = e -> logger.log(Level.SEVERE, e.getMessage());

    public static final String INSERT_USER_DML_SQL =
            "INSERT INTO USERS(name,password,email,phoneNumber) VALUES (?,?,?,?)";

    public static final String QUERY_ALL_USERS_DML_SQL = "SELECT id,name,password,email,phoneNumber FROM users";

    @Resource(name = "bean/EntityManager")
    private EntityManager entityManager;
    @Resource(name = "bean/DBConnectionManager")
    private DBConnectionManager dbConnectionManager;

    public DatabaseUserRepository() {
    }


    @Override
    public boolean save(User user) {
//        executeUpdate(INSERT_USER_DML_SQL, COMMON_EXCEPTION_HANDLER, user.getName(), user.getPassword(), user.getEmail(), user.getPhoneNumber());
        entityManager.getTransaction().begin();
        entityManager.persist(user);
        entityManager.getTransaction().commit();
        return true;
    }

    @Override
    public boolean deleteById(Long userId) {
        return false;
    }

    @Override
    public boolean update(User user) {
        return false;
    }

    @Override
    public User getById(Long userId) {
        return entityManager.find(User.class, userId);
    }

    @Override
    public User getByNameAndPassword(String userName, String password) {
        return executeQuery("SELECT id,name,password,email,phoneNumber FROM users WHERE name=? and password=?",
                this::buildUser, COMMON_EXCEPTION_HANDLER, userName, password);
    }

    @Override
    public User getByName(String userName) {
        return executeQuery("SELECT id,name,password,email,phoneNumber FROM users WHERE name=?",
                this::buildUser, COMMON_EXCEPTION_HANDLER, userName);
    }

    private User buildUser(ResultSet resultSet) throws SQLException {
        if (resultSet.next()) {
            User user = new User();
            user.setId(resultSet.getLong("id"));
            user.setName(resultSet.getString("name"));
            user.setPassword(resultSet.getString("password"));
            user.setEmail(resultSet.getString("email"));
            user.setPhoneNumber(resultSet.getString("phoneNumber"));
            return user;
        }
        return null;
    }

    @Override
    public Collection<User> getAll() {
        String hql = "from User";
        return entityManager.createQuery(hql, User.class).getResultList();
//        return executeQuery(QUERY_ALL_USERS_DML_SQL, resultSet -> {
//            // BeanInfo -> IntrospectionException
//            BeanInfo userBeanInfo = Introspector.getBeanInfo(User.class, Object.class);
//            List<User> users = new ArrayList<>();
//            while (resultSet.next()) { // 如果存在并且游标滚动 // SQLException
//                User user = new User();
//                for (PropertyDescriptor propertyDescriptor : userBeanInfo.getPropertyDescriptors()) {
//                    String fieldName = propertyDescriptor.getName();
//                    Class<?> fieldType = propertyDescriptor.getPropertyType();
//                    String methodName = resultSetMethodMappings.get(fieldType);
//                    // 可能存在映射关系（不过此处是相等的）
//                    String columnLabel = mapColumnLabel(fieldName);
//                    Method resultSetMethod = ResultSet.class.getMethod(methodName, String.class);
//                    // 通过放射调用 getXXX(String) 方法
//                    Object resultValue = resultSetMethod.invoke(resultSet, columnLabel);
//                    // 获取 User 类 Setter方法
//                    // PropertyDescriptor ReadMethod 等于 Getter 方法
//                    // PropertyDescriptor WriteMethod 等于 Setter 方法
//                    Method setterMethodFromUser = propertyDescriptor.getWriteMethod();
//                    // 以 id 为例，  user.setId(resultSet.getLong("id"));
//                    setterMethodFromUser.invoke(user, resultValue);
//                }
//                users.add(user);
//            }
//            return users;
//        }, COMMON_EXCEPTION_HANDLER);
    }

    protected int executeUpdate(String sql,
                                Consumer<Throwable> exceptionHandler, Object... args) {
        Connection connection = dbConnectionManager.getConnection();
        try {
            PreparedStatement preparedStatement = connection.prepareStatement(sql);
            for (int i = 0; i < args.length; i++) {
                Object arg = args[i];
                Class argType = arg.getClass();

                Class wrapperType = wrapperToPrimitive(argType);

                if (wrapperType == null) {
                    wrapperType = argType;
                }


                if (argType == String.class) {
                    preparedStatement.setString(i + 1, arg.toString());
                } else {
                    // Boolean -> boolean
                    String methodName = preparedStatementMethodMappings.get(argType);
                    Method method = PreparedStatement.class.getMethod(methodName, wrapperType);
                    method.invoke(preparedStatement, i + 1, arg.toString());
                }


            }
            return preparedStatement.executeUpdate();
            // 返回一个 POJO List -> ResultSet -> POJO List
            // ResultSet -> T
        } catch (Throwable e) {
            exceptionHandler.accept(e);
        }
        return 0;
    }

    /**
     * @param sql
     * @param function
     * @param <T>
     * @return
     */
    protected <T> T executeQuery(String sql, ThrowableFunction<ResultSet, T> function,
                                 Consumer<Throwable> exceptionHandler, Object... args) {
        Connection connection = dbConnectionManager.getConnection();
        try {
            PreparedStatement preparedStatement = connection.prepareStatement(sql);
            for (int i = 0; i < args.length; i++) {
                Object arg = args[i];
                Class argType = arg.getClass();

                Class wrapperType = wrapperToPrimitive(argType);

                if (wrapperType == null) {
                    wrapperType = argType;
                }

                if (argType == String.class) {
                    preparedStatement.setString(i + 1, arg.toString());
                } else {
                    // Boolean -> boolean
                    String methodName = preparedStatementMethodMappings.get(argType);
                    Method method = PreparedStatement.class.getMethod(methodName, wrapperType);
                    method.invoke(preparedStatement, i + 1, arg);
                }
            }
            ResultSet resultSet = preparedStatement.executeQuery();
            // 返回一个 POJO List -> ResultSet -> POJO List
            // ResultSet -> T
            return function.apply(resultSet);
        } catch (Throwable e) {
            exceptionHandler.accept(e);
        }
        return null;
    }


    private static String mapColumnLabel(String fieldName) {
        return fieldName;
    }

    /**
     * 数据类型与 ResultSet 方法名映射
     */
    static Map<Class, String> resultSetMethodMappings = new HashMap<>();

    static Map<Class, String> preparedStatementMethodMappings = new HashMap<>();

    static {
        resultSetMethodMappings.put(Long.class, "getLong");
        resultSetMethodMappings.put(String.class, "getString");

        preparedStatementMethodMappings.put(Long.class, "setLong"); // long
        preparedStatementMethodMappings.put(String.class, "setString"); //


    }
}
